// process_traits_posix.cpp

#include "process_traits_posix.hpp"
#include "pipe_stream.hpp"
#include <popen.hpp>

#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>

#include <boost/mpl/map.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/type_traits/is_same.hpp>

#include <sys/wait.h>


namespace io = boost::iostreams;
namespace pd = process::detail;
namespace mpl = boost::mpl;



pd::process_traits_posix::process_traits_posix(const char *cmd, int mode): in_fd_(-1), out_fd_(-1), err_fd_(-1)
{
  pid_t pid = -1;

  // Three pairs of file descriptors, for pipes connected to the
  // process' stdin, stdout and stderr
  // (stored in a single array so close_fd_array() can close all at once)
  typedef int fd_type;
  fd_type fd[] = { -1, -1, -1, -1, -1, -1 };
  fd_type* const pin = fd;
  fd_type* const pout = fd+2;
  fd_type* const perr = fd+4;

  // constants for read/write ends of pipe
  enum { RD, WR };

  // N.B.
  // For the pstreambuf pin is an output stream and
  // pout and perr are input streams.

  if (mode & process::popen::with_stdin) ::pipe(pin);

  if (mode & process::popen::with_union_stdout_and_stderr) {
    ::pipe(pout);
  }
  else {
    if (mode & process::popen::with_stdout) ::pipe(pout);

    if (mode & process::popen::with_stderr) ::pipe(perr);
  }

  pid = ::fork();
  switch (pid) {
    case 0 :
      {
        // this is the new process

        // for each open pipe close one end and redirect the
        // respective standard stream to the other end

        if (*pin >= 0)
        {
          ::close(pin[WR]);
          ::dup2(pin[RD], STDIN_FILENO);
          ::close(pin[RD]);
        }

        if (mode & process::popen::with_union_stdout_and_stderr) {
          ::close(pout[RD]);
          ::dup2(pout[WR], STDOUT_FILENO);
          ::dup2(pout[WR], STDERR_FILENO);
          ::close(pout[WR]);
        }
        else {
          if (*pout >= 0)
          {
            ::close(pout[RD]);
            ::dup2(pout[WR], STDOUT_FILENO);
            ::close(pout[WR]);
          }
          if (*perr >= 0)
          {
            ::close(perr[RD]);
            ::dup2(perr[WR], STDERR_FILENO);
            ::close(perr[WR]);
          }
        }

        execl("/bin/sh", "sh", "-c", cmd, NULL);

        // if execl failed
        throw std::runtime_error(std::string("couldn't execute: ") + cmd);
      }
    case -1 :
      {
        throw std::runtime_error("couldn't fork for some reason");
        // couldn't fork for some reason
        //error_ = errno;
        // close any open pipes
        //close_fd_array(fd);
        break;
      }
    default :
      {
        // this is the parent process, store process' pid
        pid_ = pid;

        // store one end of open pipes and close other end
        if (*pin >= 0) {
          ::close(pin[RD]);
          in_fd_ = pin[WR];
          to_ = std::auto_ptr<std::ostream>(new io::stream<io::file_descriptor_sink>(io::file_descriptor_sink(in_fd_, true)));
        }

        if (mode & process::popen::with_union_stdout_and_stderr) {
          ::close(pout[WR]);
          out_fd_ = pout[RD];
          err_fd_ = -1;
          from_ = std::auto_ptr<std::istream>(new detail::istream(out_fd_));
        }
        else {
          if (*pout >= 0) {
            ::close(pout[WR]);
            out_fd_ = pout[RD];
            from_ = std::auto_ptr<std::istream>(new detail::istream(out_fd_));
          }
          if (*perr >= 0) {
            ::close(perr[WR]);
            err_fd_ = perr[RD];
            error_ = std::auto_ptr<std::istream>(
              new io::stream<io::file_descriptor_source>(io::file_descriptor_source(err_fd_, true)));
          }
        }
      }

  }
}

int pd::process_traits_posix::poll(void) const
{
  int status;
  if (0 == ::waitpid(pid_, &status, WNOHANG))
    return -1;
  else
    return status;
}

int pd::process_traits_posix::wait(void)
{
  int status;
  ::waitpid(pid_, &status, 0);
  return status;
}

pd::process_traits_posix::~process_traits_posix()
{
    to_.reset();
    from_.reset();
    error_.reset();
}
